package com.xiaobaibai.security.token.filter;

import com.xiaobaibai.config.MyException;
import com.xiaobaibai.security.common.PropertiesInfo;
import com.xiaobaibai.security.token.token.JwtAuthenticationToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class No3JwtAuthenticationFilter extends BasicAuthenticationFilter  {

    /**
     * 关于为什么不用JwtAuthenticationFilter,它的类里已经说明白了
     * 去看BasicAuthenticationFilter源码,然后再比对简书大佬的No2filter,就是一个类型
     * todo  新感想:是不是只要是关于security原生的filter都是关于登录的,不会涉及到访问资源验证呢?
     * 解答: 根据此类,推出BasicAuthenticationFilter也可以作为访问资源的拦截,不过呢
     *       因为BasicAuthenticationFilter上面就是OncePerRequestFilter,所以Basic...只是帮我们封装了一下而已,
     *       可以说仅仅是提示了一下,所以我们可以不用它,去用OncePerRequestFilter
     *
     * 9.13博客写
     */
    /**
     * 继承BasicAuthenticationFilter 求知类
     */

    private AuthenticationManager authenticationManager;
    private PropertiesInfo propertiesInfo;
    private AntPathMatcher antPathMatcher=new AntPathMatcher();

    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

    private List<String> readyURI;

    public No3JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
        this.authenticationManager=authenticationManager;
    }

    protected String getJwtToken(HttpServletRequest request) {
        String headerToken=request.getHeader("X-Token");
        log.info("heard里面的token:"+headerToken);
        //开始基于JWT的验证

        if(StringUtils.isBlank(headerToken)){
            //返回请携带令牌
            throw new MyException("请携带令牌");
        }

        return headerToken;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        log.info("本次请求:"+request.getRequestURI());

        boolean matches = this.matches(request);
        if(!matches)//不是目标url,直接让它通过
            filterChain.doFilter(request, response);
        else {
            Authentication authResult = null;
            AuthenticationException failed = null;
            String token = getJwtToken(request);

            JwtAuthenticationToken authToken = new JwtAuthenticationToken(token);

            try {
                authResult = this.getAuthenticationManager().authenticate(authToken);
            } catch (Exception e) {
                e.printStackTrace();
                failed = (AuthenticationException) e;
            }

            /**
             * 手动加入上下文,简书大佬如此写到,小白可以去参考BasicAuthenticationFilter
             */

            if (authResult != null) {
                successfulAuthentication(request, response, filterChain, authResult);
            } else {
                unsuccessfulAuthentication(request, response, failed);
                return;
            }

            filterChain.doFilter(request, response);
        }
    }

    //这里没注册@Component当然不会触发
    @PostConstruct
    private void readyURI(){
        readyURI=new ArrayList<>();
        String matchURI=propertiesInfo.getMatchURI();
        log.info("从properInfo中取出来的uri:"+matchURI);
        String[] split = StringUtils.split(matchURI,",");
        readyURI.addAll(Arrays.asList(split));
    }

    private boolean matches(HttpServletRequest request){
        String thisURI=request.getRequestURI();
        for (String s : this.readyURI) {
            if(antPathMatcher.match(s,thisURI))
                return true;
        }
        return false;
    }

    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        failureHandler.onAuthenticationFailure(request, response, failed);
    }

    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException{
        SecurityContextHolder.getContext().setAuthentication(authResult);
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

    protected AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    public void setAuthenticationSuccessHandler(
            AuthenticationSuccessHandler successHandler) {
        Assert.notNull(successHandler, "successHandler cannot be null");
        this.successHandler = successHandler;
    }

    public void setAuthenticationFailureHandler(
            AuthenticationFailureHandler failureHandler) {
        Assert.notNull(failureHandler, "failureHandler cannot be null");
        this.failureHandler = failureHandler;
    }

    public void setPropertiesInfo(PropertiesInfo propertiesInfo) {
        this.propertiesInfo = propertiesInfo;
    }
}
